package cn.com.infcn.monitor.secure;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is a TimerTask which manages token's timeout.
 * The token contained in cache shall be generated over 
 * the user/password and login time stamp.
 * @author William
 *
 */
public class SecretTokenMgr extends TimerTask {
	protected final static Logger logger = LoggerFactory.getLogger(SecretTokenMgr.class);
	
	private final static int INIT_CAPACITY		= 512;
	private final static float LOAD_FACTOR		= 0.75f;
	private final static int DEFAULT_TIMEOUT	= 60 * 60; // 1 hour in second
	
	private static LinkedHashMap<String, Object[]> tokenMap;
	private int timeout = DEFAULT_TIMEOUT * 1000; // in second
		
	public final static String USER_SESSION_KEY = "USER_SESSION_KEY";
	public final static String USER_ID_KEY = "USER_ID_KEY";
	
	private final static ReadWriteLock rwLock = new ReentrantReadWriteLock();
	
	private static SecretTokenMgr instance = null;

	/**
	 * Constructor
	 * @param timeout - token timeout in second
	 */
	private SecretTokenMgr() {
		tokenMap = new LinkedHashMap<String, Object[]>(INIT_CAPACITY, LOAD_FACTOR, true);
	}
	
	public static SecretTokenMgr getInstance() {
		if (instance == null) {
			instance = new SecretTokenMgr();
		}
		return instance;
	}

	
	public void start(int timeout) {
		logger.info("Start SecretTokenMgr timer...");
		setTimeout(timeout);
		new Timer().schedule(this, 5000, this.timeout);
	}
	
	/**
	 * Check if the token here
	 * @param token - token string
	 * @return true if token's contained
	 */
	public boolean contain(String token) {
		Object [] o = tokenMap.get(token);
		if (o == null) {
			return false;
		}
		AtomicLong lastAccess = (AtomicLong) (o[0]);
		lastAccess.set(System.currentTimeMillis());
		return true;
	}
	
	/**
	 * get user session data
	 * @return
	 */
	public Object getSession(String token) {
		Lock lock = rwLock.readLock();
		lock.lock();
		Object[] o = tokenMap.get(token);
		lock.unlock();
		
		if (null == o) {
			return null;
		}
		
		AtomicLong lastAccess = (AtomicLong) (o[0]);
		if (lastAccess == null) {
			return null;
		}
		lastAccess.set(System.currentTimeMillis());
		return o[1];
	}
	
	/**
	 * Put token into cache.
	 * @param token
	 */
	public void put(String token, Object session) {
		logger.debug("put token:{} to session", token);
		
		AtomicLong ts = new AtomicLong(System.currentTimeMillis());
		Object[] o = {ts, session};
		
		Lock lock = rwLock.writeLock();
		lock.lock();
		tokenMap.put(token, o);
		lock.unlock();
	}
	
	public Object remove(String token) {
		Lock lock = rwLock.writeLock();
		lock.lock();
		Object[] o = tokenMap.remove(token);
		lock.unlock();
		
		if (o != null && o.length > 1) {
			return o[1];
		}
		return null;
	}
	
	/**
	 * TimerTask's template method which will check
	 * and remove time outed token from cache.
	 */
	public void run() {
		logger.info("SecretTokenMgr cleanup timeout tokens...");
		long now = System.currentTimeMillis();
		List<String> list = new ArrayList<String>();
		
		Lock rlock = rwLock.readLock();
		rlock.lock();
		Set<Map.Entry<String, Object[]>> entries = tokenMap.entrySet();
		for (Map.Entry<String, Object[]> entry : entries) {
			long lastAccess = ((AtomicLong)((entry.getValue())[0])).get();
			long time = lastAccess + timeout;
			if (time > now)
				break;
			else
				list.add(entry.getKey());
		}
		rlock.unlock();
		
		Lock wlock = rwLock.writeLock();
		wlock.lock();
		for (String token : list) {
			Object o[] = tokenMap.remove(token);
			logger.info("Remove timeout token: {}=>{}", token, ((AtomicLong)(o[0])).get());
		}
		wlock.unlock();
	}
	
	/**
	 * Set the timeout value in second.
	 * @param timeout - in second
	 */
	public void setTimeout(int timeout) {
		this.timeout = timeout * 1000;
	}

	/**
	 * Dump all token in order for debug
	 */
	void dump() {
		logger.debug("Dump all token in order");
		Set<Map.Entry<String, Object[]>> entries = tokenMap.entrySet();
		for (Map.Entry<String, Object[]> entry : entries) {
			logger.debug("token: {}=>{}", entry.getKey(), ((AtomicLong)(entry.getValue()[0])).get());
		}
	}
}
